//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2006
// (c) Nikita Karnauhov (jinx@ac-sw.com), 2006
//
// Generalize objects.
//

# include "StdAfx.h"
# include "Resource.h"
# include "GeneralizeRgn.h"
# include "UndoRedo.h"
# include "Label.h"
# include "Generalize.h"
# include "SetElementPoints.h"
# include "Map.h"
# include "Selection.h"
# include "CombinedAction.h"
# include "Status.h"
# include "MapView.h"
# include "Globals.h"
# include "GeneralizeMap.h"

float GetGeneralizationThreshold (size_t _cLevel) {
	CCritSec::CLock lock (g_cs);
	const size_t cBits = (_cLevel >= g_map.Levels.size () ? 24 : g_map.Levels [_cLevel].btBits);
//	return cBits <= 24 ? .5f*360.f/(1 << cBits) : 0;
	return .5f*360.f/(1 << cBits);
}

static
size_t _GeneralizeElement (rgn_t & _rgn, size_t _cElement, float _fCosY, bool _bApplyDirectly) {
	assert (_rgn.IsPolyline () || _rgn.IsPolygon ());
	assert (_cElement < _rgn.elements.size ());
	rgn_element_t & element = _rgn.elements [_cElement];

	assert (element.cLevel < g_map.Levels.size ());
	const float d = GetGeneralizationThreshold (element.cLevel);
	if (d == 0)
		return 0;

	size_t cDeletedPoints = 0;

	const points_t & pointsOld = element.points;
	const size_t cPointsOld = pointsOld.size ();
	if (cPointsOld <= 2)
		return 0;

	points_t points;
	points.reserve (cPointsOld);

	point_extras_t PointExtras;
	if (element.pExtra) {
		point_extras_t & PointExtrasOld = element.pExtra->PointExtras;
		PointExtrasOld.resize (cPointsOld);

		size_t cPrevNode = 0;
		points     .push_back (pointsOld      [0]);
		PointExtras.push_back (PointExtrasOld [0]);

		for (size_t c = 1; c < cPointsOld; ++ c) {
			if (! PointExtrasOld [c].empty () || c == cPointsOld - 1) {
				points_t cut (pointsOld.begin () + cPrevNode, pointsOld.begin () + c + 1);
				cDeletedPoints += Generalize (cut, d, _fCosY);

				assert (cut [0] == pointsOld [cPrevNode]);
				points.insert (points.end (), cut.begin () + 1, cut.end ());

				const size_t cPointsNew = points.size ();
				PointExtras.resize (cPointsNew);
				PointExtras [cPointsNew - 1] = PointExtrasOld [c];
					
				cPrevNode = c;
			}
		}
	} else {
		points = element.points;
		cDeletedPoints = Generalize (points, d, _fCosY);
	}

	if (cDeletedPoints != 0) {
		if (_bApplyDirectly) {
			element.points.swap (points);

			if (element.pExtra || IsNotEmpty (PointExtras))
				element.pExtra->PointExtras.swap (PointExtras);
		} else {
			SetElementPoints (& _rgn, _cElement, points, PointExtras);
		}
	}

	return cDeletedPoints;
}

void GeneralizeElement (rgn_t & _rgn, size_t _cElement) {
	_GeneralizeElement (_rgn, _cElement, g_map.fCosY, false);
}

void GeneralizeRgnInLevel (rgn_t & _rgn, size_t _cLevel) {
	assert (_rgn.IsPolyline () || _rgn.IsPolygon ());
	assert (_cLevel < g_map.Levels.size ());

	const int iDepth = BeginCombinedAction ();

	const size_t cElements = _rgn.elements.size ();
	for (size_t cElement = 0; cElement < cElements; ++ cElement) {
		rgn_element_t & element = _rgn.elements [cElement];
		if (element.bDeleted)
			continue;
		if (element.cLevel != _cLevel)
			continue;

		GeneralizeElement (_rgn, cElement);
	}

	string_t strAction = "generalize [";
	GetHintText (_rgn, strAction);
	strAction.append ("]");
	EndCombinedAction (iDepth, strAction.c_str ());
}

/*
void GeneralizeSelection () {
	if (g_Selection.empty ())
		return;

	CCritSec::CLock lock (g_cs);

	const int iDepth = BeginCombinedAction ();

	const size_t cSelItems = g_Selection.size ();
	for (size_t cSelItem = 0; cSelItem < cSelItems; ++ cSelItem) {
		find_info_t & SelItem = g_Selection [cSelItem];
		if (SelItem.pLayer) {
			rgn_t & rgn = * SelItem.iRgn;
			if (rgn.IsDeleted ())
				continue;
			if (! rgn.IsPolyline () && ! rgn.IsPolygon ())
				continue;

			if (SelItem.cElementOrOrigin != -1) {
				assert (SelItem.cElementOrOrigin < rgn.elements.size ());
				rgn_element_t & element = rgn.elements [SelItem.cElementOrOrigin];
				if (element.bDeleted)
					continue;

				GeneralizeElement (rgn, element);
			} else {
				const size_t cElements = rgn.elements.size ();
				for (size_t cElement = 0; cElement < cElements; ++ cElement) {
					rgn_element_t & element = rgn.elements [cElement];
					if (element.bDeleted)
						continue;

					GeneralizeElement (rgn, element);
				}
			}
		}
	}

	EndCombinedAction (iDepth, "generalize [multiple objects]");
}
*/
////////////////////////////////////////////////////////

class CGeneralizePointsAction : public CEditAction {
  public:
	CGeneralizePointsAction (bool _bSilent) : m_bSilent (_bSilent) {}

	virtual void GetName (string_t & _str) const {_str.append ("generalize all polylines and polygons");}
	virtual cu_t CanUndo () const {return cuBackup;}
	virtual bool Do ();
	virtual void Undo () {}
	virtual void Redo () {}

  public:
	const bool m_bSilent;
};

bool CGeneralizePointsAction::Do () {
	size_t cDeletedPoints = 0;
	size_t cDeletedElements = 0;
	size_t cDeletedRgns = 0;

	{
		CCritSec::CLock lock (g_cs);

		const size_t cTotalRgns = g_map.layers [rkPolyline].cRgns + g_map.layers [rkPolygon].cRgns;
		if (cTotalRgns == 0)
			return true;
		size_t cRgnsProcessed = 0;

		SetStatus (GetStr (IDS_STATUS_GENERALIZING));
		SetProgressStatus (0);

		for (size_t cKind = rkPolyline; cKind <= rkPolygon; ++ cKind) {
			rgns_t & rgns = g_map.layers [cKind].rgns;
			for (rgns_t::iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
				rgn_t & rgn = * iRgn;
				if (rgn.IsDeleted () || rgn.IsWTR ())
					continue;

				size_t cRestElements = 0;
				const size_t cElements = rgn.elements.size ();
				for (size_t cElement = 0; cElement < cElements; ++ cElement) {
					rgn_element_t & element = rgn.elements [cElement];
					if (element.bDeleted)
						continue;

					cDeletedPoints += _GeneralizeElement (rgn, cElement, g_map.fCosY, true);

					if (cKind == rkPolygon && element.points.size () < 3) {
						element.bDeleted = true;
						++ cDeletedElements;
					} else
						++ cRestElements;
				}
				if (cRestElements == 0) {
					rgn.Delete (g_map);
					++ cDeletedRgns;
				}
			}

			++ cRgnsProcessed;
			SetProgressStatus (cRgnsProcessed*100/cTotalRgns);
		}
	}

	SetStatusReady ();
	Refresh ();

	if (! m_bSilent) {
		WarningMsgBox (
			"The number of deleted\n"
			"  points = %d\n"
			"  elements = %d\n"
			"  objects = %d.",
			cDeletedPoints, cDeletedElements, cDeletedRgns
		);
	}

	return true;
}

void GeneralizePoints (bool _bSilent) {
	PerformEditAction (new CGeneralizePointsAction (_bSilent));
}

class CGeneralizeAdjacentAction : public CEditAction {
  public:
	CGeneralizeAdjacentAction (bool _bSilent) : m_bSilent (_bSilent) {}

	virtual void GetName (string_t & _str) const {_str.append ("generalize all polylines and polygons (preserving boundaries)");}
	virtual cu_t CanUndo () const {return cuBackup;}
	virtual bool Do ();
	virtual void Undo () {}
	virtual void Redo () {}

  public:
	const bool m_bSilent;
};

bool CGeneralizeAdjacentAction::Do () {
	size_t cDeletedPoints = 0;
	size_t cDeletedElements = 0;
	size_t cDeletedRgns = 0;

	CCritSec::CLock lock (g_cs);

	SetStatus (GetStr (IDS_STATUS_GENERALIZING));
	GeneralizeMap (cDeletedPoints, cDeletedElements, cDeletedRgns);
	SetStatusReady ();
	Refresh ();

	if (! m_bSilent)
		WarningMsgBox (
			"The number of deleted\n"
			"  points = %d\n"
			"  elements = %d\n"
			"  objects = %d.",
			cDeletedPoints, cDeletedElements, cDeletedRgns);

	return true;
};

void GeneralizeAdjacent (bool _bSilent) {
	PerformEditAction (new CGeneralizeAdjacentAction (_bSilent));
}
